package com.ruoyi.common.utils.calculate;

import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.core.pad.ChartLineDTO;
import com.ruoyi.common.core.pad.LinePoint;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 贝塞尔曲线控制点计算
 */
public class BezierCurveUtil {
    /**
     * 先算出相邻原始点的中点，
     * 再把相邻中点连成的线段平移到对应的原始点，以平移后的中点作为控制点，相邻原始点为起始点画贝塞尔曲线，这样就保证了连接处的光滑。而贝塞尔曲线本身是光滑的，所以就把这些原始点用光滑曲线连起来了。
     * <p>
     * <p>
     * 绘制穿过多边形顶点的平滑曲线
     * 用三阶贝塞尔曲线实现
     */
    public static ChartLineDTO calPoints(String listJson, float step, boolean isCilpe) {
        List<Float> yVelList = new ArrayList<>();
        if (listJson != null) {
            yVelList = JSON.parseArray(listJson, Float.class);
        }
        return calPoints(yVelList, 0.8F, step, isCilpe);
    }


    public static ChartLineDTO calPoints(List<Float> yVelList, float step, boolean isCilpe) {
        if (yVelList == null || yVelList.size() == 0) {
            return null;
        }
        return calPoints(yVelList, 0.8F, step, isCilpe);
    }

    public static ChartLineDTO calPoints(List<Float> xVelList, List<Float> yVelList, boolean isCilpe) {
        if (yVelList == null || yVelList.size() == 0) {
            return null;
        }
        if (xVelList == null || xVelList.size() == 0) {
            return null;
        }
        return calPoints(yVelList, 0.8F, xVelList, isCilpe);
    }


    /**
     * @param yVelList
     * @param k
     * @param step
     * @param isCilpe  是否零值补点
     * @return
     */
    public static ChartLineDTO calPoints(List<Float> yVelList, float k, float step, boolean isCilpe) {

        if (yVelList == null || yVelList.size() == 0) {
            return null;
        }

        // 曲线数值点集合
        List<LinePoint> linePointList = new ArrayList<>();
        // 最大值
        LinePoint maxYLinePoint = new LinePoint(), minYLinePoint = new LinePoint(), maxXLinePoint = new LinePoint(), minXLinePoint = new LinePoint();
        // 控制点
        List<LinePoint> controlLinePointList = new ArrayList<LinePoint>();
        // 实例化数据点集合，得到Y值最大、最小的数据点
        for (int i = 0; i < yVelList.size(); i++) {
            float thisY = yVelList.get(i);
            // 零点补值
            if (i == 0 && isCilpe) {
                linePointList.add(new LinePoint(0, thisY));
            }
            LinePoint thisP = new LinePoint(step * (isCilpe ? i + 1 : i), thisY);
            linePointList.add(thisP);
            //三个点位以上时候计算控制点
            if(linePointList.size() > 2) {
                controlLinePointList.addAll(getControlPoint(linePointList, k));
            }
            if (i == 0) {
                maxYLinePoint = thisP;
                minYLinePoint = thisP;
            } else {
                maxYLinePoint = maxYLinePoint.y > thisY ? maxYLinePoint : thisP;
                minYLinePoint = minYLinePoint.y < thisY ? minYLinePoint : thisP;
            }
        }
        if (linePointList.size() != 0) {
            maxXLinePoint = linePointList.get(linePointList.size() - 1);
            minXLinePoint = linePointList.get(0);
        }
        //如果生成点位小于3
        if(linePointList.size() <= 2) {
            controlLinePointList.add(0, linePointList.get(0));
            controlLinePointList.add(linePointList.get(linePointList.size() - 1));
        }

        //// 计算控制点
        //
        //int size = linePointList.size();
        //// 计算相邻原始点的中点
        //LinePoint[] midLinePoints = new LinePoint[size];
        //for (int i = 0; i < size; i++) {
        //    LinePoint p1 = linePointList.get(i);
        //    LinePoint p2 = linePointList.get((i + 1) % size);
        //    midLinePoints[i] = new LinePoint((p1.x + p2.x) / 2f, (p1.y + p2.y) / 2f);
        //}
        //// 计算比例点
        //LinePoint[] ratioLinePoints = new LinePoint[size];
        //for (int i = 0; i < size; i++) {
        //    LinePoint p0 = linePointList.get(i);
        //    LinePoint p1 = linePointList.get((i + 1) % size);
        //    LinePoint p2 = linePointList.get((i + 2) % size);
        //
        //    // 根据距离，计算比率（<1）
        //    double l1 = distance(p0, p1);
        //    double l2 = distance(p1, p2);
        //    double ratio = l1 / (l1 + l2);
        //
        //    // 计算比例点
        //    LinePoint mp0 = midLinePoints[i];
        //    LinePoint mp1 = midLinePoints[(i + 1) % size];
        //    ratioLinePoints[i] = ratioPointConvert(mp1, mp0, ratio);
        //}
        //
        //// 移动线段，计算控制点
        //LinePoint[] controlLinePoints = new LinePoint[size * 2];
        //for (int i = 0, j = 0; i < size; i++) {
        //    LinePoint ratioLinePoint = ratioLinePoints[i];
        //    LinePoint verLinePoint = linePointList.get((i + 1) % size);
        //    float dx = ratioLinePoint.x - verLinePoint.x;
        //    float dy = ratioLinePoint.y - verLinePoint.y;
        //    LinePoint controlLinePoint1 = new LinePoint(midLinePoints[i].x - dx, midLinePoints[i].y - dy);
        //    LinePoint controlLinePoint2 = new LinePoint(midLinePoints[(i + 1) % size].x - dx, midLinePoints[(i + 1) % size].y - dy);
        //    controlLinePoints[j++] = ratioPointConvert(controlLinePoint1, verLinePoint, k);
        //    controlLinePoints[j++] = ratioPointConvert(controlLinePoint2, verLinePoint, k);
        //}
        //controlLinePoints[size * 2 - 1] = linePointList.get(0);
        //controlLinePoints[size * 2 - 4] = linePointList.get(linePointList.size() - 1);


        ChartLineDTO chartLineDto = new ChartLineDTO();
        chartLineDto.setMaxYLinePoint(maxYLinePoint);
        chartLineDto.setMinYLinePoint(minYLinePoint);
        chartLineDto.setMaxXLinePoint(maxXLinePoint);
        chartLineDto.setMinXLinePoint(minXLinePoint);
        chartLineDto.setLinePointList(linePointList);
        chartLineDto.setControlLinePointList(controlLinePointList);
        return chartLineDto;
    }

    /**
     * @param yVelList
     * @param k
     * @param xVelList
     * @return
     */
    public static ChartLineDTO calPoints(List<Float> yVelList, float k, List<Float> xVelList, boolean isCilpe) {

        if (yVelList == null || yVelList.size() == 0) {
            return null;
        }

        if (xVelList == null || xVelList.size() == 0) {
            return null;
        }

        // 曲线数值点集合
        List<LinePoint> linePointList = new ArrayList<>();
        // 最大值
        LinePoint maxYLinePoint = new LinePoint(), minYLinePoint = new LinePoint(), maxXLinePoint = new LinePoint(), minXLinePoint = new LinePoint();
        // 控制点
        List<LinePoint> controlLinePointList = new ArrayList<LinePoint>();
        // 实例化数据点集合，得到Y值最大、最小的数据点
        for (int i = 0; i < yVelList.size(); i++) {
            float thisY = yVelList.get(i);
            float thisX = xVelList.get(i);
            // 零点补值
            if (i == 0 && isCilpe) {
                linePointList.add(new LinePoint(0, thisY));
            }
            LinePoint thisP = new LinePoint(thisX, thisY);
            linePointList.add(thisP);
            //三个点位以上时候计算控制点
            if(linePointList.size() > 2) {
                controlLinePointList.addAll(getControlPoint(linePointList, k));
            }
            if (i == 0) {
                maxYLinePoint = thisP;
                minYLinePoint = thisP;
            } else {
                maxYLinePoint = maxYLinePoint.y > thisY ? maxYLinePoint : thisP;
                minYLinePoint = minYLinePoint.y < thisY ? minYLinePoint : thisP;
            }
        }
        if (linePointList.size() != 0) {
            maxXLinePoint = linePointList.get(linePointList.size() - 1);
            minXLinePoint = linePointList.get(0);
        }
        //如果生成点位小于3
        if(linePointList.size() <= 2) {
            controlLinePointList.add(0, linePointList.get(0));
            controlLinePointList.add(linePointList.get(linePointList.size() - 1));
        }
        //// 计算控制点
        //
        //int size = linePointList.size();
        //// 计算相邻原始点的中点
        //LinePoint[] midLinePoints = new LinePoint[size];
        //for (int i = 0; i < size; i++) {
        //    LinePoint p1 = linePointList.get(i);
        //    LinePoint p2 = linePointList.get((i + 1) % size);
        //    midLinePoints[i] = new LinePoint((p1.x + p2.x) / 2f, (p1.y + p2.y) / 2f);
        //}
        //// 计算比例点
        //LinePoint[] ratioLinePoints = new LinePoint[size];
        //for (int i = 0; i < size; i++) {
        //    LinePoint p0 = linePointList.get(i);
        //    LinePoint p1 = linePointList.get((i + 1) % size);
        //    LinePoint p2 = linePointList.get((i + 2) % size);
        //
        //    // 根据距离，计算比率（<1）
        //    double l1 = distance(p0, p1);
        //    double l2 = distance(p1, p2);
        //    double ratio = l1 / (l1 + l2);
        //
        //    // 计算比例点
        //    LinePoint mp0 = midLinePoints[i];
        //    LinePoint mp1 = midLinePoints[(i + 1) % size];
        //    ratioLinePoints[i] = ratioPointConvert(mp1, mp0, ratio);
        //}
        //
        //// 移动线段，计算控制点
        //LinePoint[] controlLinePoints = new LinePoint[size * 2];
        //for (int i = 0, j = 0; i < size; i++) {
        //    LinePoint ratioLinePoint = ratioLinePoints[i];
        //    LinePoint verLinePoint = linePointList.get((i + 1) % size);
        //    float dx = ratioLinePoint.x - verLinePoint.x;
        //    float dy = ratioLinePoint.y - verLinePoint.y;
        //    LinePoint controlLinePoint1 = new LinePoint(midLinePoints[i].x - dx, midLinePoints[i].y - dy);
        //    LinePoint controlLinePoint2 = new LinePoint(midLinePoints[(i + 1) % size].x - dx, midLinePoints[(i + 1) % size].y - dy);
        //    controlLinePoints[j++] = ratioPointConvert(controlLinePoint1, verLinePoint, k);
        //    controlLinePoints[j++] = ratioPointConvert(controlLinePoint2, verLinePoint, k);
        //}
        //controlLinePoints[size * 2 - 1] = linePointList.get(0);
        //controlLinePoints[size * 2 - 4] = linePointList.get(linePointList.size() - 1);


        ChartLineDTO chartLineDto = new ChartLineDTO();
        chartLineDto.setMaxYLinePoint(maxYLinePoint);
        chartLineDto.setMinYLinePoint(minYLinePoint);
        chartLineDto.setMaxXLinePoint(maxXLinePoint);
        chartLineDto.setMinXLinePoint(minXLinePoint);
        chartLineDto.setLinePointList(linePointList);
        chartLineDto.setControlLinePointList(controlLinePointList);
        return chartLineDto;
    }

    /**
     * 统一计算三阶贝塞尔曲线控制点
     */
    private static List<LinePoint> getControlPoint(List<LinePoint> pointList, float k) {
        List<LinePoint> controlLinePointList = new ArrayList<>();
        if (pointList == null || pointList.size() < 3) {
            return controlLinePointList;
        }
        LinePoint p0 = pointList.get(pointList.size() - 3);
        LinePoint p1 = pointList.get(pointList.size() - 2);
        LinePoint p2 = pointList.get(pointList.size() - 1);

        float x0 = p0.x, y0 = p0.y;
        float x1 = p1.x, y1 = p1.y;
        float x2 = p2.x, y2 = p2.y;

        // 求中点
        float mx1 = (x0 + x1) / 2f;
        float my1 = (y0 + y1) / 2f;
        float mx2 = (x1 + x2) / 2f;
        float my2 = (y1 + y2) / 2f;

        // 计算比例点
        double l1 = distance(p0, p1);
        double l2 = distance(p1, p2);
        double ratio = l1 / (l1 + l2);
        float rx = ratioPointConvert(mx2, mx1, ratio);
        float ry = ratioPointConvert(my2, my1, ratio);

        // 移动线段，计算控制点
        float dx = rx - x1;
        float dy = ry - y1;

        float cp1x = mx1 - dx, cp1y = my1 - dy;
        float cp2x = mx2 - dx, cp2y = my2 - dy;

        float cp1_x = ratioPointConvert(cp1x, x1, k), cp1_y = ratioPointConvert(cp1y, y1, k);
        float cp2_x = ratioPointConvert(cp2x, x1, k), cp2_y = ratioPointConvert(cp2y, y2, k);

        //控制点微调
        cp1_x = Math.max(cp1_x, p0.x);
        cp2_x = Math.min(cp2_x, p2.x);
        cp1_y = Math.max(cp1_y, 0);
        cp2_y = Math.max(cp2_y, 0);

        controlLinePointList.add(new LinePoint(cp1_x, cp1_y));
        controlLinePointList.add(new LinePoint(cp2_x, cp2_y));
        return controlLinePointList;
    }


    /**
     * 计算两点之间的距离
     */
    private static double distance(LinePoint p1, LinePoint p2) {
        return Math.sqrt(((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)));
    }

    /**
     * 比例点转换
     */
    private static LinePoint ratioPointConvert(LinePoint p0, LinePoint p1, double ratio) {
        LinePoint ratioP = new LinePoint();
        ratioP.x = (float) (ratio * (p0.x - p1.x) + p1.x);
        ratioP.y = (float) (ratio * (p0.y - p1.y) + p1.y);
        return ratioP;
    }

    /**
     * 比例点转换
     */
    private static float ratioPointConvert(float v1, float v2, double ratio) {
        float v = (float) (ratio * (v1 - v2) + v2);
        return v;
    }

    public static Float getSelectPoints(ChartLineDTO line, float touchX) {
        float maxXVel = 0.0F;
        float maxVel = line.getMaxXLinePoint().x;
        maxXVel = maxXVel > maxVel ? maxXVel : maxVel;
        touchX = touchX < maxXVel ? touchX : maxXVel;
        List<LinePoint> pointList = line.getLinePointList();
        List<LinePoint> controlPointList = line.getControlLinePointList();
        pointList = new LinkedList<>(pointList);
        controlPointList = new LinkedList<>(controlPointList);
        if (pointList != null && pointList.size() != 0) {
            float x0 = 0.0F;
            float x1 = 0.0F;
            float x2 = 0.0F;
            float x3 = 0.0F;
            float y0 = 0.0F;
            float y1 = 0.0F;
            float y2 = 0.0F;
            float y3 = 0.0F;
            if (controlPointList == null || controlPointList.size() < 2
                    || (pointList.get(0) != controlPointList.get(0) && pointList.get(pointList.size() - 1) != controlPointList.get(controlPointList.size() - 1))) {
                controlPointList.add(0, pointList.get(0));
                controlPointList.add(pointList.get(pointList.size() - 1));
            }
            for (int j = 0; j < pointList.size() - 1; ++j) {
                LinePoint thisP = (LinePoint) pointList.get(j);
                LinePoint nextP = (LinePoint) pointList.get(j + 1);
                if (thisP.x <= touchX && touchX <= nextP.x) {
                    LinePoint cpp1 = (LinePoint) controlPointList.get(2 * j);
                    LinePoint cpp2 = (LinePoint) controlPointList.get(2 * j + 1);
                    x0 = thisP.x;
                    x1 = cpp1.x;
                    x2 = cpp2.x;
                    x3 = nextP.x;
                    y0 = thisP.y;
                    y1 = cpp1.y;
                    y2 = cpp2.y;
                    y3 = nextP.y;
                    break;
                }
            }

            float t = 0.5F;
            float tMin = 0.0F;
            float tMax = 1.0F;

            float tX;
            for (int j = 0; j < 300; ++j) {
                ++j;
                t = (tMax + tMin) / 2.0F;
                tX = x0 * (1.0F - t) * (1.0F - t) * (1.0F - t) + 3.0F * x1 * t * (1.0F - t) * (1.0F - t) + 3.0F * x2 * t * t * (1.0F - t) + x3 * t * t * t;
                if (tX < touchX) {
                    tMin = t;
                }

                if (tX > touchX) {
                    tMax = t;
                }

                if (Math.abs(tX - touchX) < 0.001f) {
                    break;
                }
            }
            tX = y0 * (1.0F - t) * (1.0F - t) * (1.0F - t) + 3.0F * y1 * t * (1.0F - t) * (1.0F - t) + 3.0F * y2 * t * t * (1.0F - t) + y3 * t * t * t;
            return Float.parseFloat(String.format("%.4f", tX));
        } else {
            return 0.0F;
        }
    }

}
